package lpg;

import lpg.util.DispatchWriter;
import lpg.util.Utility;

class Control
{
    Control(Option option, LexStream lexStream,
            LookupTable<String, VariableSymbol> variableTable, LookupTable<String, MacroSymbol> macroTable)
    {
        this.option         = option;
        this.lexStream      = lexStream;
        this.variableTable  = variableTable;
        this.macroTable     = macroTable;
        grammar             = new Grammar(this);
        base                = new Base(this);
        dfa                 = new Dfa(this);
        produce             = new Produce(this);
        pda                 = new Pda(this);

        //
        // Flush buffer to print options and initial reports
        //
        if (!option.quiet)
            option.flushReport();
    }

    void processGrammar()
    {
        //TODO
    }

    void constructParser()
    {
        //TODO
    }

    void process()
    {
        Action action = new Action(this, option.actionBlocks(), grammar, macroTable);

        //
        // ProcessGrammar:
        //
        grammar.processInput(action);

        //
        // By the time this function is invoked, the source input has already
        // been fully processed. More specifically, the scanner is invoked in the
        // main program, Main.java. The parser and the grammar are constructed
        // and invoked in the constructor of this object.
        //

        //
        // ConstructParser:
        //

        //
        // If the user only wanted to edit his grammar, we quit the program.
        //
        if (option.edit)
        {
            if (option.first || option.follow || option.xref)
            {
                base.processBasicMaps();
                grammar.processGrammarOutput(action);
                base.processBaseOutput();
            }

            printBasicStatistics();

            return;
        }

        base.processBasicMaps();                       // Build basic maps
        dfa.processDFA();                              // build state automaton
        produce.processAdvancedMaps();                 // build scopes maps and others,...
        grammar.processActions(action, base, produce); // generate AST nodes and actions
        pda.processPDA();                              // Build Reduce map

        grammar.processGrammarOutput(action);
        base.processBaseOutput();
        produce.processScopesOutput();
        pda.processPDAOutput();

        //
        // Free up some space!
        //
        macroTable.reset();     // macro table no longer needed.

        //
        // If the tables are requested, we process them.
        //
        Generator generator = null;
        Table table = null;
        if (option.table)
        {
            if (option.gotoDefault && option.ntCheck)
                option.emitError(0, "The options GOTO_DEFAULT and NT_CHECK are incompatible. Tables not generated");
            else
            {
                generator = new Generator(this);
                generator.processCompressionAndGeneration();
                table = new Table(this);
                generator.generate(table);
                table.outputTables();
                if (option.states)
                    table.printStateMaps();
            }
        }

        printAllStatistics(generator, table);

        option.flushReport();
    }

    private void printBasicStatistics()
    {
        if (option.quiet)
            return;

        option.report.append(Util.LINE_SEPARATOR);
        option.report.append("Number of Terminals: ");
        option.report.append(grammar.numTerminals - 1); // -1 for %Empty
        option.report.append(Util.LINE_SEPARATOR);
        option.report.append("Number of Nonterminals: ");
        option.report.append(grammar.numNonterminals - 1); // -1 for %Acc
        option.report.append(Util.LINE_SEPARATOR);
        option.report.append("Number of Productions: ");
        option.report.append(grammar.numRules + 1);
        option.report.append(Util.LINE_SEPARATOR);

        if (option.singleProductions)
        {
            option.report.append("Number of Single Productions: ");
            option.report.append(grammar.numSingleProductions);
            option.report.append(Util.LINE_SEPARATOR);
        }

        option.report.append("Number of Items: ");
        option.report.append(grammar.numItems);
        option.report.append(Util.LINE_SEPARATOR);

        option.flushReport();
    }

    private void printAllStatistics(Generator generator, Table table)
    {
        if (option.quiet)
            return;

        printBasicStatistics();

        if (option.scopes)
        {
            option.report.append("Number of Scopes: ");
            option.report.append(produce.scopePrefix.length);
            option.report.append(Util.LINE_SEPARATOR);
        }

        option.report.append("Number of States: ");
        option.report.append(dfa.numStates);
        option.report.append(Util.LINE_SEPARATOR);

        if (pda.maxLaState > dfa.numStates)
        {
            option.report.append("Number of look-ahead states: ");
            option.report.append(pda.maxLaState - dfa.numStates);
            option.report.append(Util.LINE_SEPARATOR);
        }

        option.report.append("Number of Shift actions: ");
        option.report.append(dfa.numShifts);
        option.report.append(Util.LINE_SEPARATOR);

        option.report.append("Number of Goto actions: ");
        option.report.append(dfa.numGotos);
        option.report.append(Util.LINE_SEPARATOR);

        if (option.readReduce)
        {
            option.report.append("Number of Shift/Reduce actions: ");
            option.report.append(dfa.numShiftReduces);
            option.report.append(Util.LINE_SEPARATOR);
            option.report.append("Number of Goto/Reduce actions: ");
            option.report.append(dfa.numGotoReduces);
            option.report.append(Util.LINE_SEPARATOR);
        }

        option.report.append("Number of Reduce actions: ");
        option.report.append(pda.numReductions);
        option.report.append(Util.LINE_SEPARATOR);

        if (!pda.notLrk)
        {
            option.report.append("Number of Shift-Reduce conflicts: ");
            option.report.append(pda.numShiftReduceConflicts);
            option.report.append(Util.LINE_SEPARATOR);
            option.report.append("Number of Reduce-Reduce conflicts: ");
            option.report.append(pda.numReduceReduceConflicts);
            option.report.append(Util.LINE_SEPARATOR);
        }

        if (!grammar.keywords.isEmpty())
        {
            option.report.append("Number of Keyword/Identifier Shift conflicts: ");
            option.report.append(pda.numShiftShiftConflicts);
            option.report.append(Util.LINE_SEPARATOR);
            option.report.append("Number of Keyword/Identifier Shift-Reduce conflicts: ");
            option.report.append(pda.numSoftShiftReduceConflicts);
            option.report.append(Util.LINE_SEPARATOR);
            option.report.append("Number of Keyword/Identifier Reduce-Reduce conflicts: ");
            option.report.append(pda.numSoftReduceReduceConflicts);
            option.report.append(Util.LINE_SEPARATOR);
        }

        if (generator != null)
        {
            option.report.append(generator.getReport());
            table.printReport();
        }

        option.flushReport();
    }

    void printHeading()
    {
        Header header = new Header();
        header.setOut(option._syslis);
        header.run();
    }

    void exit()
    {
        invalidateFile(option.prsFile, option.prsType);
        invalidateFile(option.symFile, option.symType);
        if (!grammar.exportedSymbols.isEmpty())
            invalidateFile(option.expFile, option.expType);
        switch (option.programmingLanguage)
        {
            case CPP:
            case C:
                 invalidateFile(option.dclFile, option.dclType);
                 break;
            case PLXASM:
            case PLX:
                 invalidateFile(option.dclFile, option.dclType);
                 invalidateFile(option.defFile, option.defType);
                 invalidateFile(option.impFile, option.impType);
                 break;
            case JAVA:
                 break;
            case ML:
                 invalidateFile(option.dclFile, option.dclType);
                 invalidateFile(option.impFile, option.impType);
                 break;
            case XML:
                 break;
            default:
                 break;
        }

        //
        // Before exiting, flush the report buffer.
        //
        option.flushReport();

        throw new LpgExitException();
    }

    private void invalidateFile(String filename, String filetype)
    {
        DispatchWriter out = null;
        try
        {
            out = new DispatchWriter();
            invalidator.setOut(out);
            invalidator.run(option, filename, filetype);

            out.close();
            out = null;
        }
        finally
        {
            Utility.close(out);
        }
    }

    final Option option;
    final LexStream lexStream;
    final LookupTable<String, VariableSymbol> variableTable;
    final LookupTable<String, MacroSymbol> macroTable;
    final Grammar grammar;
    final Base base;
    final Dfa dfa;
    final Produce produce;
    final Pda pda;

    private final FileInvalidator invalidator = new FileInvalidator();

    static final int SYMBOL_SIZE        = 256;
    static final int PRINT_LINE_SIZE    = 80;

    static final String HEADER_INFO = "LALR Parser Generator";
    static final String VERSION = "2.0.21";

}
